import json
import pprint
import os
import time


class ResourceDevice:
    """
    代表所有设备的配置类， 字段动态定义
    """

    def __init__(self, name='', *args, **kwargs):
        self.name = name
        self.type = kwargs.get('type')
        self.description = kwargs.get('description', None)
        # ports：保存设备端口对象的实例
        self.ports = dict()

    def add_port(self, name, *args, **kwargs):
        """
        添加端口
        :param name:
        :param args:
        :param kwargs:
        :return:
        """
        if name in self.ports:
            raise FileExistsError('Port Name {} already exists'.format(name))
        self.ports[name] = DevicePort(self, name, args, kwargs)

    def to_dict(self):
        """
        序列化
        :return:dict
        """
        ret = dict()
        for key, value in self.__dict__.items():
            if key == 'ports':
                ret[key] = dict()
                for port_name, port in value.items():
                    ret[key][port_name] = port.to_dict()
            else:
                ret[key] = value
        return ret

    @staticmethod
    def from_dict(dict_obj: dict):
        """
        反序列化
        :param dict_obj:
        :return: ResourceDevice
        """
        ret = ResourceDevice()
        for key, value in dict_obj.items():
            if key == 'ports':
                ports = dict()
                for port_name, port in value.items():
                    ports[port_name] = DevicePort.from_dict(port, ret)
                setattr(ret, 'ports', ports)
            else:
                setattr(ret, key, value)
        return ret


class DevicePort:
    """
    代表设备的连接端口
    """

    def __init__(self, parent_device: ResourceDevice = None, name='', *args, **kwargs):
        """
        :param parent_device: 父对象实例
        :param name:
        :param args:
        :param kwargs:
        """
        self.parent: ResourceDevice = parent_device
        self.name: str = name
        self.description: str = kwargs.get('description', None)
        # remote_ports:存储其连接的远端端口的实例
        self.remote_ports = list()

    def to_dict(self):
        """
        序列化
        :return:dict
        """
        ret = dict()
        for key, value in self.__dict__.items():
            if key == 'parent':
                ret[key] = value.name
            elif key == 'remote_ports':
                ret[key] = list()
                for remote_port in value:
                    ret[key].append(
                        {
                            "device": remote_port.parent.name,
                            "port": remote_port.name
                        }
                    )
            else:
                ret[key] = value

        return ret

    @staticmethod
    def from_dict(dict_obj: dict, parent):
        ret = DevicePort(parent)
        for key, value in dict_obj.items():
            if key == 'remote_ports' or key == 'parent':
                continue
            setattr(ret, key, value)
        return ret


class ResourcePool:
    """
    资源池，负责资源的序列化和反序列化，以及储存和读取
    """

    def __init__(self):
        self.topology = dict()
        self.information = dict()
        self.reserved = None
        self.file_name = None
        self.owner = None

    def reserve(self):
        if self.file_name is None:
            raise ResourceError('load a resource file first')
        self.load(self.file_name, self.owner)
        self.reserved = {'owner': self.owner, 'data': time.strftime('%Y/%m/%d %H:%M:%S', time.localtime())}
        self.save(self.file_name)

    def release(self):
        if self.file_name is None:
            raise ResourceError('load a resource file first')
        self.load(self.file_name, self.owner)
        self.reserved = None
        self.save(self.file_name)

    def add_device(self, device_name, **kwargs):
        if device_name in self.topology:
            raise ResourceError(f'device {device_name} already exists')
        self.topology[device_name] = ResourceDevice(device_name)

    def load(self, filename, owner):
        if os.path.exists(filename):
            raise ResourceError(f'Cannot find file {filename}')
        self.topology.clear()
        self.reserved = False
        self.information = dict()

        # 读取配置资源的JSON字符串
        with open(filename) as file:
            json_object = json.load(file)
        if 'info' in json_object:
            self.information = json_object['info']
        if 'reserved' in json_object and json_object['reversed'] is not None and json_object['reversed']['owner'] != owner:
            raise ResourceError(f"Resource is reversed by {json_object['reversed']['owner']}")
        self.owner = owner
        for key, value in json_object['device'].items():
            device = ResourceDevice.from_dict(value)
            self.topology[key] = device

        # 映射所有设备的连接关系
        for key, device in json_object['device'].items():
            for port_name, port in device['port'].items():
                for remote_port in port['remote_port']:
                    remote_port_obj = self.topology[remote_port['device']].ports[remote_port['port']]
                    self.topology[key].ports[port_name].remote_ports.append(remote_port_obj)

    def save(self, filename):
        with open(filename, mode='w') as file:
            root_object = dict()
            root_object['device'] = dict()
            root_object['info'] = self.information
            for device_key, device in self.topology.items():
                root_object['device'][device_key] = device.to_dict()
            json.dump(root_object, file, indent=4)


class ResourceError(Exception):
    pass


if __name__ == '__main__':
    switch = ResourceDevice(name='switch')
    switch.add_port('ETH1/1')
    switch.add_port('ETH1/2')

    switch2 = ResourceDevice(name='switch2')
    switch2.add_port('ETH1/1')
    switch2.add_port('ETH1/2')

    switch.ports['ETH1/1'].remote_ports.append(switch2.ports['ETH1/1'])
    switch2.ports['ETH1/1'].remote_ports.append(switch.ports['ETH1/1'])

    # print(json.dumps(switch.to_dict(), indent=4))
    print(DevicePort.from_dict(switch.ports['ETH1/1'].to_dict(), switch).to_dict())
    rp = ResourcePool()
    rp.information = "hahahha"
    rp.topology['switch1'] = switch
    rp.topology['switch2'] = switch2
    rp.save("test.json")
    # rp.load('./test.json')
    print('done')
    print(rp.__dict__)
